﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using MySql.Data.MySqlClient;
using snat.proxy.items;
using snat.EventFramework.Errors;
using snat.proxy.sqlapi;
using System.Diagnostics;
using System.ComponentModel;

namespace snat.proxy {

    public class ListProxy<T> : System.Collections.Generic.IEnumerable<T>
            where T : Item {

        private int start = 0;
        private int bufferSize = 50;            //the size of the buffer used in memory i.e. on ListProxy<T>[i] request
        private int iteratorBufferSize = 1000;   //the size of the buffer used in the iterator, i.e. on ListProxy<T>.GetEnumerator();

        /// <summary>
        /// The main SQL query for this list
        /// </summary>
        /// <remarks>
        /// This ListProxy will be of the same size as the results of this query
        /// </remarks>
        private string sqlQuery;
        /// <summary>
        /// The queries which retrieve extra data about what is retrieved by sqlQuery
        /// <see cref="ListProxy.sqlQuery"/>
        /// </summary>
        /// <remarks>
        /// It is used to perform local data joins and thereby simplifying sql statements and speeding them up
        /// </remarks>
        private string[] secondaryQuery;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_sqlQuery"><see cref="sqlQuery"/></param>
        /// <param name="_secondaryQuery"></param>
        internal ListProxy(string _sqlQuery, params string[] _secondaryQuery) {
            this.sqlQuery = _sqlQuery;
            this.secondaryQuery = _secondaryQuery;
            // if (typeof(T) == typeof(Message)) iteratorBufferSize = 250;
        }

        List<T> cache = null;
        public T this[int i] {
            get {
                //Contract.Requires<ArgumentNullException>(i >= 0 && i < Count, "i must be within the range of 0 and Count");
                if (cache != null && i >= start && i < start + bufferSize) {
                    return cache[i - start];
                } else {
                    Console.Write("Readloading the cache..");
                    DateTime startTime = DateTime.Now;
                    cache = GetItems(i, bufferSize);
                    TimeSpan elapsed = DateTime.Now - startTime;
                    Console.WriteLine("done.\t" + elapsed.Milliseconds + "ms");
                    start = i;
                    return cache[0];
                }
            }
        }

        private int? count = null;
        public int Count {
            get {
                if (count == null) {
                    try {
                        return Query.CountOfQuery(sqlQuery);
                    } catch (MySqlException ex) {
                        throw ex;
                    }
                }
                return (int)count;
            }
        }

        delegate List<T> GetItemsDelegate(int position, int bufferSize);

        public IEnumerator<T> GetEnumerator() {
            Stopwatch stopwatch = new Stopwatch();
            GetItemsDelegate getConnections = new GetItemsDelegate(GetItems);
            List<T>[] buffer = new List<T>[2]; //2 is the number of buffers
            IAsyncResult[] asyncResult = new IAsyncResult[2];
            asyncResult[0] = getConnections.BeginInvoke(0, iteratorBufferSize, null, null);
            int nextBuffer = 0;
            for (int i = 0; i < Count; i += iteratorBufferSize) {
                Console.Write("Readloading the buffer position {0}..", i);

                stopwatch.Start();
                buffer[nextBuffer] = getConnections.EndInvoke(asyncResult[nextBuffer]);
                nextBuffer = 1 - nextBuffer; // 0->1, 1->0
                asyncResult[nextBuffer] = getConnections.BeginInvoke(i + iteratorBufferSize, iteratorBufferSize, null, null);
                stopwatch.Stop();
                Console.WriteLine("done.\t" + stopwatch.ElapsedMilliseconds + "ms");
                stopwatch.Reset();

                foreach (T c in buffer[1 - nextBuffer]) {
                    yield return c;
                }
            }
            getConnections.EndInvoke(asyncResult[nextBuffer]);
        }

        private List<T> GetItems(int position, int bufferSize) {
            List<T> ret = new List<T>();
            DataSet set = null;
            DataSet[] aux = new DataSet[secondaryQuery.Length];
            try {
                MySqlCommand command = new MySqlCommand(sqlQuery + " LIMIT " + position + ", " + bufferSize);
                set = DataProxy.Instance.RunQuery(command, position, bufferSize);

                for (int i = 0; i < secondaryQuery.Length; i++) {
                    MySqlCommand auxQuery = new MySqlCommand(secondaryQuery[i]);
                    aux[i] = DataProxy.Instance.RunQuery(auxQuery, position, bufferSize);
                }

                Type type = typeof(T);
                object[] parameters = new object[2];
                parameters[1] = aux;
                foreach (DataRow row in set.Tables["Table"].Rows) {
                    parameters[0] = row;
                    ret.Add((T)Activator.CreateInstance(type, parameters));
                }
            } catch (MySqlException ex) {

                throw ex;
            }
            return ret;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

    }
}
